package showtime.framework.engine
{
	/**
	 * Sprite2D.as
	 * Yin Hao
	 * @version 1.0.0
	 * 
	 * Description.
	 * 
	 * Copyright (c) 2012 Yin Hao
	 * 
	 * Permission is hereby granted, free of charge, to any person obtaining a copy
	 * of this software and associated documentation files (the "Software"), to deal
	 * in the Software without restriction, including without limitation the rights
	 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 * copies of the Software, and to permit persons to whom the Software is
	 * furnished to do so, subject to the following conditions:
	 * 
	 * The above copyright notice and this permission notice shall be included in
	 * all copies or substantial portions of the Software.
	 * 
	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	 * THE SOFTWARE.
	 */
	
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import showtime.framework.engine.animation.Animation;
	

	public class Sprite2D extends Entity2D
	{				
		
		public var direction:int = 0;
		/**
		 * Whether the current animation has finished its first (or only) loop. 
		 */		
		public var finished:Boolean = false;
		/**
		 * The width of the actual graphic or image being displayed (not necessarily the game object/bounding box).
		 * NOTE: Edit at your own risk!!  This is intended to be read-only.
		 */
		public var frameWidth:int = 0;
		/**
		 * The width of the actual graphic or image being displayed (not necessarily the game object/bounding box).
		 * NOTE: Edit at your own risk!!  This is intended to be read-only.
		 */
		public var frameHeight:int = 0;		
		/**
		 * Image buffer for cut frame pixels. 
		 */		
		protected var buffer:BitmapData = null;		
		/**
		 * 
		 */		
		protected var frameTimer:Number = 0;
		/**
		 * Collection for animations. 
		 */		
		protected var animations:Vector.<Animation> = null;
		/**
		 * Now Animation. 
		 */
		protected var nowAnimation:Animation = null;
		/**
		 *  Now Frame. 
		 */		
		protected var nowFrame:int = 0;		
		/**
		 * The frame for render. 
		 */		
		protected var renderFrame:int = 0;

		/**
		 * Animation callback. 
		 */		
		private var _callback:Function = null;		
		/**
		 * Whether or not the buffer for this sprite needs to be recalculated. 
		 */		
		private var change:Boolean = false;
		
		internal var _flashZeroPoint:Point = null;
		
		internal var _flashRect:Rectangle = null;
		
		static public const DEFAULT_GRAPHIC_WIDTH:int = 16;		
		static public const DEFAULT_GRAPHIC_HEIGHT:int = 16;
				
		public function Sprite2D(x:Number = 0, y:Number = 0, key:String = null, graphic:BitmapData = null)
		{
			super(x, y);
			
			_flashZeroPoint = new Point();
			_flashRect = new Rectangle();
			animations = new Vector.<Animation>;
			
			graphic == null ? createGraphic(DEFAULT_GRAPHIC_WIDTH, DEFAULT_GRAPHIC_HEIGHT) : loadGraphic(key, graphic);
			
		}
			
		public function loadGraphic(key:String, graphic:BitmapData, reverse:Boolean = false, graphicWidth:int = 0, graphicHeight:int = 0):Sprite2D
		{			
			buffer = Cache.addBitmap(key, graphic, reverse);
			
			frameHeight = graphicHeight;			
			frameWidth = graphicWidth;
			
			if (frameWidth == 0)
			{				
				frameWidth = buffer.width;				
			}
			
			if (frameHeight == 0)
			{				
				frameHeight = buffer.height;				
			}
			
			configGraphic();
			
			return this;	
		}
		
		private function configGraphic():void
		{	
			_flashRect.x = 0;
			_flashRect.y = 0;
			_flashRect.width = frameWidth;
			_flashRect.height = frameHeight;
	
			if (bitmapData != null)
			{
				bitmapData.dispose();
				bitmapData = null;				
			}
			
			bitmapData = new BitmapData(frameWidth, frameHeight);			
			bitmapData.copyPixels(buffer, _flashRect, _flashZeroPoint);
		}
			
		private function createGraphic(graphicWidth:int, graphicHeight:int, color:uint = 0xFFFFFFFF, key:String = null):Sprite2D
		{
			buffer = Cache.createBitmap(null, graphicWidth, graphicHeight, color);
			
			frameHeight = buffer.height;
			
			frameWidth = buffer.width;
			
			configGraphic();
			
			return this;
		}
		
		override public function destroy():void
		{	
			super.destroy();
			
			for (var index:int = 0, length:int = animations.length; index < length; index++)
			{
				animations[index].dispose();
			}
			
			animations = null;			
			buffer = null;
			
			parent != null && parent.removeChild(this);
			
		}
	
		private function updateAnimation():void
		{
			if ((nowAnimation != null) && (nowAnimation.delay > 0) && (nowAnimation.looped || !finished))
			{
				frameTimer += World2D.elapsed;
				
				while (frameTimer > nowAnimation.delay)
				{
					frameTimer = frameTimer - nowAnimation.delay;
					
					if (nowFrame == nowAnimation.frames.length - 1)
					{
						if (nowAnimation.looped)
						{
							nowFrame = 0;
						}					
						else
						{
							finished = true;
						}
	
					}
					else
					{
						nowFrame++;
					}
					
					renderFrame = nowAnimation.frames[nowFrame];
					change = true;
				}
			}
		}
		
		override public function update():void
		{			
			super.update();
			
			active && updateAnimation();
		}
		
		override public function render():void
		{			
			super.render();
			
			visible && change && calculateFrame();
		}
			
		public function play(animationName:String, force:Boolean = false):void
		{
			if (!force && (nowAnimation != null) && (animationName == nowAnimation.name) && (nowAnimation.looped || !finished))
			{
				return;
			}
			
			nowFrame = 0;			
			renderFrame = 0;			
			frameTimer = 0;			
			var index:int = 0;			
			var length:int = animations.length;
			
			while (index < length)
			{
				if (animations[index].name == animationName)
				{
					nowAnimation = animations[index];
					
					if (nowAnimation.delay <= 0)
					{					
						finished = true;
					}
					else
					{
						finished = false;
					}
					
					renderFrame = nowAnimation.frames[nowFrame];					
					change = true;
					
					return;
				}
				
				index++;
			}
			
		}
		
		/**
		 * Adds a new animation to the sprite.
		 *  
		 * @param name What this animation should be called (e.g "run").
		 * @param frames An array of numbers indicating what frames to play in what order (e.g. 1,2,3).
		 * @param frameRate The speed in frames per second that the animation should play at (e.g. 40 fps).
		 * @param looped Whether or not the animation is looped or just plays once.
		 * 
		 */		
		public function addAnimation(name:String, frames:Array, frameRate:Number = 0, looped:Boolean = true):void
		{
			animations.push(new Animation(name, frames, frameRate, looped));
		}
		
		public function addAnimationCallback(callback:Function):void
		{
			_callback = callback;
		}
		/**
		 * Internal function that performs the actual sprite rendering, called by render(). 
		 * 
		 */		
		private function calculateFrame():void
		{
			var offsetX:int = renderFrame * frameWidth;			
			var offsetY:int = 0;
			
			var graphicWidth:int = buffer.width;
			
			if (offsetX >= graphicWidth)
			{
				offsetY = int(offsetX / graphicWidth) * frameHeight;
				offsetX %= graphicWidth;
			}
			
			_flashRect.x = offsetX;
			_flashRect.y = offsetY;			
			
			bitmapData.copyPixels(buffer, _flashRect, _flashZeroPoint);			
			_flashRect.x = _flashRect.y = 0;			
			
			if (_callback != null)
			{
				_callback();
			}
			
			change = false;
		}

		
		/////////////////////////////////////////
		//				Getter & Setters
		////////////////////////////////////////

		/**
		 * The X coordinate of the left side of the rectangle.  Read-only.
		 */
		public function get left():Number
		{
			return x;
		}
		
		/**
		 * The X coordinate of the right side of the rectangle.  Read-only.
		 */
		public function get right():Number
		{
			return x + width;
		}
		
		/**
		 * The Y coordinate of the top of the rectangle.  Read-only.
		 */
		public function get top():Number
		{
			return y;
		}
		
		/**
		 * The Y coordinate of the bottom of the rectangle.  Read-only.
		 */
		public function get bottom():Number
		{
			return y + height;
		}
	}
}